package model;

import gameAppLayer.enums.Constants;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;

import controller.IController;

import utilities.CoordConvert;
import utilities.IDGenerator;
import utilities.Key;
import view.AbstractView;
import model.universe.Cache;
import model.universe.EventObject;
import model.universe.Item;

/*
 * Holds relevant energy, mass, volume, temp, etc. parameters for in-game objects with full physical presence.
 */
public class PhysicalDataModel implements IPhysicalDataModel {
	
	//private AbstractModel model;
	
	private HashMap<String, Double> variables;
	
//	protected Double mass = 0.0;  //kg	
//	protected Double volume = 0.0;  //m3	
//	protected Double density = 0.0;  //kg/m3	
//	protected Double fullEnergy = 0.0;  //Unsure of units at this time.  Exajoules?	
//	//energy sub-types
//	protected Double massEnergy = 0.0; //energy to be gained from direct conversion	
//	protected Double spinEnergy = 0.0; //energy that can be extracted from spin	
//	protected Double radiationEnergy = 0.0; //energy that can be extracted from solar radiation/light	
//	protected Double gravEnergy = 0.0; //energy that can be extracted from gravity well	
//	protected Double magneticEnergy = 0.0; //energy that can be extracted from magnetic field	
	
	protected Vector2f systemLocation = new Vector2f().zero(); //location when in-system (at a destination)
	protected int[] hexLocation = new int[3]; //hex tile location on main board.
	protected Vector2f velocity = new Vector2f().zero();
	
//	protected Double age = 0.0;  //yrs		
//	protected Double gravity = 0.0;  //G's	
//	protected Double rotation = 0.0;  //rotations/sec		
//	protected Double temperature = 0.0;  //K	
//	protected Double radius = 0.0;  //km	
//	protected Double orbitalRadius = 0.0;  //km	
//	protected Double luminosity = 0.0;  //W	
//	protected Double magField = 0.0;  //Gauss	
	
	//game characteristics
	protected ArrayList<Key> gameEventList = new ArrayList<Key>();
	protected ArrayList<Key> cacheList = new ArrayList<Key>();
	protected ArrayList<Key> itemList = new ArrayList<Key>();
	protected ArrayList<Key> puzzleList = new ArrayList<Key>();
	
	protected ArrayList<String> names = new ArrayList<String>();
	
	protected IPhysicalDataModelContainer container; //optional, set to provide reference to container
	private boolean containerFlag = false;
	
	//private Key ownerKey;	
	protected Double hitPoints;
	
	public PhysicalDataModel(HashMap<String, Double> variables) {
		this.variables = variables;
		variables.put("mass", new Double(0.0));
		variables.put("volume", new Double(0.0));
		variables.put("density", new Double(0.0));
		variables.put("fullEnergy", new Double(0.0));
		variables.put("massEnergy", new Double(0.0));
		variables.put("spinEnergy", new Double(0.0));
		variables.put("radiationEnergy", new Double(0.0));
		variables.put("gravEnergy", new Double(0.0));
		variables.put("magneticEnergy", new Double(0.0));
		
		variables.put("age", new Double(0.0));
		variables.put("gravity", new Double(0.0));
		variables.put("rotation", new Double(0.0));
		variables.put("temperature", new Double(0.0));
		variables.put("radius", new Double(0.0));
		variables.put("orbitalRadius", new Double(0.0));
		variables.put("luminosity", new Double(0.0));
		variables.put("magField", new Double(0.0));
	}
	
	//For use if physicalModel is placed within a container
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#setContainer(model.PhysicalContainerModel)
	 */
	public void setContainer(IPhysicalDataModelContainer container) {
		this.container = container;
		containerFlag = true;		
	}
	
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#removeContainer(model.PhysicalContainerModel)
	 */
	public void removeContainer(IPhysicalDataModelContainer container) {
		this.container = null;
		containerFlag = false;
	}
	
	//updates all variables that are calculated rather than set
	private void updateParameters() {
		
		double radius = variables.get("radius");
		double volume = variables.get("volume");
		
		
		//must at least have a radius and volume that are non-zero, otherwise get a divide by zero error
		if ((radius>0) && (volume!=0)) {
			double mass = variables.get("mass");
			double rotation = variables.get("rotation");
			double luminosity = variables.get("luminosity");
			double gravity = Constants.GravitationalConstant*mass/(radius*1000*radius*1000)/Constants.EarthGravity;
			double magField = variables.get("magField");
			
			variables.put("volume", 4*Math.PI*radius*radius*radius/3);			
			variables.put("gravity", gravity);
			variables.put("density", mass/volume);
			//this.volume = 4*Math.PI*radius*radius*radius/3;
			//this.gravity = Constants.GravitationalConstant*mass/(radius*1000*radius*1000)/Constants.EarthGravity;
			//this.density = mass/volume;
			
			double massEnergy = mass*Constants.SpeedOfLight*Constants.SpeedOfLight;//Emass = mc2
			double spinEnergy = 0.5 * (0.4*mass*radius*radius) * 2*Math.PI*rotation;
			double radiationEnergy = luminosity;
			double gravEnergy = volume*gravity*gravity*Constants.EarthGravity*Constants.EarthGravity/(8*Math.PI*Constants.GravitationalConstant);
			double magneticEnergy = volume* magField*magField*0.5/Constants.MagneticPermeability;
			
			variables.put("massEnergy", massEnergy); 
			variables.put("spinEnergy", spinEnergy); //Espin = 1/2 * I * w2
			variables.put("radiationEnergy", radiationEnergy);
			variables.put("gravEnergy", gravEnergy);
			variables.put("magneticEnergy", magneticEnergy);
			variables.put("fullEnergy", massEnergy + spinEnergy + radiationEnergy + gravEnergy + magneticEnergy);
			
			//this.massEnergy = mass*Constants.SpeedOfLight*Constants.SpeedOfLight; 
			//this.spinEnergy = 0.5 * (0.4*mass*radius*radius) * 2*Math.PI*rotation; 
			//this.radiationEnergy = luminosity;
			//this.gravEnergy = volume*gravity*gravity*Constants.EarthGravity*Constants.EarthGravity/(8*Math.PI*Constants.GravitationalConstant);
			//this.magneticEnergy = volume* magField*magField*0.5/Constants.MagneticPermeability;
			//this.fullEnergy = massEnergy + spinEnergy + radiationEnergy + gravEnergy + magneticEnergy;
						
			if (containerFlag) {
				container.updateParameters();
			}
		}
		
		
		
		//update views
	}
	
	/*
	 * Set-able variables:
	 * Mass, Location, Velocity, Age, Rotation, Temperature, Radius, OrbitalRadius, Luminosity, MagField.
	 * 
	 * All others calculated
	 */
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getMass()
	 */
	public Double getMass() {
		return variables.get("mass");
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#changeMass(double)
	 */
	public void changeMass(double deltaMass) {
		double mass = variables.get("mass");
		mass += deltaMass;
		variables.put("mass", mass);
		updateParameters();
		//update views
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#setMass(double)
	 */
	public void setMass(double mass) {
		variables.put("mass", mass);
		updateParameters();
		//update views
	}
	//---------------------------------------------
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getVolume()
	 */
	public Double getVolume() {
		return variables.get("volume");
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getDensity()
	 */
	public Double getDensity() {
		return variables.get("density");
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getFullEnergy()
	 */
	public Double getFullEnergy() {
		return variables.get("fullEnergy");
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getMassEnergy()
	 */
	public Double getMassEnergy() {
		return variables.get("massEnergy");
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getSpinEnergy()
	 */
	public Double getSpinEnergy() {
		return variables.get("spinEnergy");
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getRadiationEnergy()
	 */
	public Double getRadiationEnergy() {
		return variables.get("radiationEnergy");
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getGravEnergy()
	 */
	public Double getGravEnergy() {
		return variables.get("gravEnergy");
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getMagneticEnergy()
	 */
	public Double getMagneticEnergy() {
		return variables.get("magneticEnergy");
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getLocation()
	 */
	public Vector2f getLocation() {
		return this.systemLocation;
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#setLocation(com.jme3.math.Vector2f)
	 */
//	public void setLocation(Vector2f location) {
//		this.location = location;
//		//not sure if this is ever used...
//		controller.processOutgoingEvent(new LocationBroadcastEvent(this.key, this.location));
//		//update views
//	}
	
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getHexLocation()
	 */
	public int[] getHexLocation() {
		return this.hexLocation;
	}
	
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#setHexLocation(java.util.ArrayList)
	 */
//	public void setHexLocation(ArrayList<Integer> location) {
//		this.hexLocation.clear();
//		this.hexLocation.addAll(location);
//		setLocation(CoordConvert.hex2CartVec(location));	
//	}
	
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getVelocity()
	 */
	public Vector2f getVelocity() {
		return this.velocity;
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#setVelocity(com.jme3.math.Vector2f)
	 */
	public void setVelocity(Vector2f velocity) {
		this.velocity = velocity;
		//update views
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getAge()
	 */
	public Double getAge() {
		return variables.get("age");
	}
	
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#setAge(double)
	 */
	public void setAge(double age) {
		variables.put("age", age);		
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getGravity()
	 */
	public Double getGravity() {
		return variables.get("gravity");
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getRotation()
	 */
	public Double getRotation() {
		return variables.get("rotation");
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#setRotation(double)
	 */
	public void setRotation(double rotation) {
		variables.put("rotation", rotation);
		updateParameters();		
	}

	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getTemperature()
	 */
	public Double getTemperature() {
		return variables.get("temperature");
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#setTemperature(double)
	 */
	public void setTemperature(double temperature) {
		variables.put("temperature", temperature);
		updateParameters();
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getRadius()
	 */
	public Double getRadius() {
		return variables.get("radius");
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#setRadius(double)
	 */
	public void setRadius(double radius) {
		variables.put("radius", radius);
		updateParameters();		
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getOrbitalRadius()
	 */
	public Double getOrbitalRadius() {
		return variables.get("orbitalRadius");
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#setOrbitalRadius(double)
	 */
	public void setOrbitalRadius(double orbitalRadius) {
		variables.put("orbitalRadius", orbitalRadius);
		//update views
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getLuminosity()
	 */
	public Double getLuminosity() {
		return variables.get("luminosity");
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#setLuminosity(double)
	 */
	public void setLuminosity(double luminosity) {
		variables.put("luminosity", luminosity);
		updateParameters();
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getMagField()
	 */
	public Double getMagField() {
		return variables.get("magField");
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#setMagField(double)
	 */
	public void setMagField(double magField) {
		variables.put("magField", magField);
		updateParameters();
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getGameEventList()
	 */
	public ArrayList<Key> getGameEventList() {
		return this.gameEventList;
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#addGameEvent(utilities.Key)
	 */
	public void addGameEvent(Key gameEvent) {
		this.gameEventList.add(gameEvent);
		//update views
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#removeGameEvent(utilities.Key)
	 */
	public void removeGameEvent(Key gameEvent) {
		this.gameEventList.remove(gameEvent);
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getCacheList()
	 */
	public ArrayList<Key> getCacheList() {
		return this.cacheList;
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#addCache(utilities.Key)
	 */
	public void addCache(Key cache) {
		this.cacheList.add(cache);
		//update views
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#removeCache(utilities.Key)
	 */
	public void removeCache(Key cache) {
		this.cacheList.remove(cache);
		//update views
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getItemList()
	 */
	public ArrayList<Key> getItemList() {
		return this.itemList;
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#addItem(utilities.Key)
	 */
	public void addItem(Key item) {
		this.itemList.add(item);
		//update views
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#removeItem(utilities.Key)
	 */
	public void removeItem(Key item) {
		this.itemList.remove(item);
		//update views
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getPuzzleList()
	 */
	public ArrayList<Key> getPuzzleList() {
		return this.puzzleList;
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#addPuzzle(utilities.Key)
	 */
	public void addPuzzle(Key puzzle) {
		this.puzzleList.add(puzzle);
		//update views
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#removePuzzle(utilities.Key)
	 */
	public void removePuzzle(Key puzzle) {
		this.puzzleList.remove(puzzle);
		//update views
	}
	//---------------------------------------------
	
	
	//---------------------------------------------
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getNameList()
	 */
	public ArrayList<String> getNameList() {
		return this.names;
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#addName(java.lang.String)
	 */
	public void addName(String name) {
		this.names.add(name);
		//update views
	}
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#removeName(java.lang.String)
	 */
	public void removeName(String name) {
		this.names.remove(name);
		//update views
	}	
	/* (non-Javadoc)
	 * @see model.IPhysicalModel#getCurrentName()
	 */
	public String getCurrentName() {
		return this.names.get(names.size()-1);
	}
	//---------------------------------------------

//	public void attach(AbstractView view) {
//		model.attach(view);
//	}
//
//	public void detach(AbstractView view) {
//		model.detach(view);
//	}
//
//	public void updateViews() {
//		model.updateViews();
//	}
//
//	public void setController(IController controller) {
//		model.setController(controller);
//	}
//
//	public IController getController() {
//		return model.getController();
//	}
//
//	public Key getKey() {
//		return model.getKey();
//	}
//
//	public void setKey(Key key) {
//		model.setKey(key);
//	}
//
//	public int getKeyValue() {
//		return model.getKeyValue();
//	}
	
	
	
	
}
